package cn.hutool.postfix.generator;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.map.multi.ListValueMap;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.RuntimeUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import cn.hutool.postfix.generator.common.Config;
import cn.hutool.postfix.generator.lang.Lang;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.Parameter;
import com.sun.javadoc.RootDoc;
import com.sun.tools.javadoc.Main;

import java.io.File;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.Collectors;


/**
 * JavaDoc的使用方法
 *
 * @author liuxiongkang
 */
public class RuleDocletGenerator {
    protected static final String TEMP_PATH = System.getProperty("user.dir").concat("\\templates\\");
    private static final String BUILD_ERROR = "[ERROR]";
    private static final Log LOG = LogFactory.get(RuleDocletGenerator.class);
    private static final boolean INHERIT_PARENT_METHOD = Config.COMMON_CONFIG.getBool("parent.method.inherit", false);
    private static final String OBJ_CLASS_NAME = Object.class.getName();
    private static RootDoc rootDoc;

    private RuleDocletGenerator() {
        //do nothing
    }

    /**
     * 根据支持的 lang 分别生成hutool-all.postfixTemplates , 且复制到 指定 idea-plugin  位置
     */
    protected static void generateAllRuleFile() {
        String filePathDir = Config.COMMON_CONFIG.get("hutool-all.file.path");
        File templateDir = FileUtil.file(filePathDir);
        if (!FileUtil.exist(templateDir.getParentFile()) && !FileUtil.exist(templateDir.getParentFile().getParentFile())) {
            LOG.warn("hutool-all.file.path={} 设置目录不存在, 请检查是否修改为当前电脑idea 插件的路径!", filePathDir);
        }
        Config.LANG.forEach(lang -> {
            String allRule = FileUtil.loopFiles(TEMP_PATH.concat(lang.lang())).stream().map(file -> "######## " + file.getName() + " ######## \n" + FileUtil.readUtf8String(file)).collect(Collectors.joining("\n"));
            String allFile = TEMP_PATH.concat(lang.lang().concat("/hutool-all.postfixTemplates"));
            FileUtil.writeUtf8String(allRule, allFile);
            LOG.info("生成hutool-all文件:{}", allFile);
            if (CharSequenceUtil.isNotBlank(filePathDir)) {
                String filePath = filePathDir + StrPool.C_SLASH + lang.lang();
                FileUtil.copy(allFile, filePath, true);
                LOG.info("复制hutool-all 到 {}", filePath);
            }
        });
    }

    /**
     * 利用  mvn dependency:build-classpath 收集每个模块依赖信息 ,扫描每个模块下的 Util 类,生成对应的 postfix 规则
     */
    protected static void collectBuildInfo(File pomFile, Map<String, List<String>> utilClassMap) {
        String componentName = pomFile.getParentFile().getName().toLowerCase();
        List<String> utilClassList = utilClassMap.get("cn." + componentName.replace("-", "."));
        if (CollUtil.isEmpty(utilClassList)) {
            LOG.warn("模块[ {} ]下未找到xxxUtil, 跳过规则生成", componentName);
            return;
        }
        // 利用  mvn dependency:build-classpath 收集 模块依赖信息
        String buildInfo;
        RuntimeException ex = null;
        try {
            String buildInfoCommand = RuntimeUtil.execForStr(CharSequenceUtil.format("cmd /c cd /d {} && {}", pomFile.getParentFile().getAbsolutePath(), Config.DEPENDENCY_MVN_COMMAND));
            buildInfo = ReUtil.getGroup1("Dependencies classpath:\r\n(.*?)\r\n", buildInfoCommand);
            if (null != buildInfo && buildInfo.contains(BUILD_ERROR)) {
                LOG.warn("{}  获取dependency:build-classpath 含有错误:{}", componentName, buildInfo);
            }
        } catch (RuntimeException e) {
            buildInfo = Config.COMMON_CONFIG.get("dependency.build.info.default.".concat(componentName));
            ex = e;
        }
        if (CharSequenceUtil.isBlank(buildInfo)) {
            throw null == ex ? new UtilException("模块{} 获取dependency:build-classpath失败", componentName) : ex;
        }
        LOG.info("===============================================================================");
        LOG.info("收集模块[ {} ]的buildInfo = {}", componentName, buildInfo);
        // 扫描每个模块下的 Util 类,获取对应的 postfix 规则元数据
        Map<String, Set<Tuple>> clazzMap = collect(componentName, buildInfo, utilClassList);
        // 生成具体规则
        generateRules(componentName, utilClassList.size(), clazzMap);
    }

    /**
     * 扫描每个模块下的 Util 类,获取对应的 postfix 规则元数据
     */
    private static Map<String, Set<Tuple>> collect(String componentName, String buildInfo, List<String> utilClassList) {
        // 扫描指定 类中的注释, 必须也指定类中的依赖的 jar包 ; rootDoc 非线程安全
        classDocletInfoCollect(componentName, buildInfo, utilClassList);
        // 同一模块下,不会有类名完全一样的
        List<Tuple> rulesMetaInfo = new ArrayList<>(utilClassList.size() * 10);
        ListValueMap<String, String> clazzInheritMap = new ListValueMap<>(utilClassList.size());
        metaInfoCollect(rulesMetaInfo, clazzInheritMap);
        // 先分组转为 class -> tuple
        Map<String, Set<Tuple>> clazzMap = rulesMetaInfo.stream().collect(Collectors.groupingBy(t -> t.get(0), Collectors.toSet()));
        // 如果要继承父类方法, 就把 没子类的类 找出, 再继承父类方法;    如果不继承父类方法, 直接生成postfix规则
        if (INHERIT_PARENT_METHOD) {
            Map<String, Set<Tuple>> subclassMap = rulesMetaInfo.stream().filter(t -> !clazzInheritMap.containsKey(t.get(0).toString())).collect(Collectors.groupingBy(t -> t.get(0), Collectors.toSet()));
            subclassMap.forEach((clazzName, methodInfos) -> addSuperInfo(clazzName, methodInfos, clazzMap, clazzInheritMap));
            clazzMap.clear();
            clazzMap.putAll(subclassMap);
        }
        return clazzMap;
    }

    /**
     * 生成具体规则,写到文件
     */
    private static void generateRules(String componentName, int clazzSize, Map<String, Set<Tuple>> clazzMap) {
        Config.LANG.forEach(lang -> {
            List<String> ruleDataList = new ArrayList<>();
            LongAdder count = new LongAdder();
            clazzMap.forEach((clazzName, methodInfos) -> {
                ruleDataList.add("######## ".concat(clazzName));
                //同一个方法名分组      methodInfo : 0 类全名,  1 父类全名,  2 方法名, 3 方法第一个参数类型, 4 方法第一行注释, 5 方法参数个数>2
                CollUtil.sort(ObjectUtil.cloneByStream(methodInfos), Comparator.comparing(Object::toString))//
                        .stream().filter(infos -> !lang.isExclude(infos.get(3), infos.get(2))).collect(Collectors.groupingBy(t -> t.get(2))).forEach((mdName, mdInfos) -> {
                            ruleDataList.add("." + mdName + " : " + mdInfos.get(0).get(4));
                            //方法第一个参数一样, 参数也大于1个的 只保留1个
                            Collection<List<Tuple>> groupFirstParamInfos = mdInfos.stream().map(t -> convertParam(lang, t)).collect(Collectors.groupingBy(t -> t.get(3))).values();
                            CollUtil.sort(groupFirstParamInfos, Comparator.comparing(ts -> ts.get(0).get(0).toString()));
                            groupFirstParamInfos.forEach(infos -> {
                                boolean multiParam = infos.stream().anyMatch(t -> t.get(5));
                                CollUtil.sort(infos, Comparator.comparing(Tuple::toString));
                                Tuple info = infos.get(0);
                                // 参数根据lang 不同, 转为不同类型
                                String left = CharSequenceUtil.format("{} [{}]", info.get(3), clazzName);
                                String classLang = lang.classConvert(clazzName);
                                String right = multiParam ? CharSequenceUtil.format("{}.{}($expr$, $arg$)", classLang, mdName) : CharSequenceUtil.format("{}.{}($expr$)", classLang, mdName);
                                String rightImport = lang.staticSupport() && Config.STATIC_IMPORT ? "[USE_STATIC_IMPORTS]" : "";
                                ruleDataList.add(CharSequenceUtil.format("\t{}  →  {} {}", left, right, rightImport));
                                count.increment();
                            });
                            ruleDataList.add("");
                        });
                ruleDataList.add("\n");
            });
            LOG.info("[ {} {} ] 生成 util个数:{} 规则数:{}", lang.lang(), componentName, clazzSize, count.intValue());
            FileUtil.writeUtf8Lines(ruleDataList, TEMP_PATH.concat(lang.lang() + StrPool.C_SLASH + componentName + ".postfixTemplates"));
        });
    }

    private static Tuple convertParam(Lang lang, Tuple t) {
        t.getMembers()[3] = lang.paramTypeConvert(t.get(3));
        return t;
    }

    /**
     * 扫描类信息, 获取源数据
     */
    private static void classDocletInfoCollect(String componentName, String buildInfo, List<String> utilClassList) {
        String sourcePath = Config.SOURCE_PATH.replaceFirst("hutool-.*", "").concat(componentName).concat("/src/main/java/");
        String[] sourceJavaArr = utilClassList.stream().map(name -> sourcePath.concat(name.replace(".", String.valueOf(StrPool.C_SLASH)).replace("/java", ".java"))).toArray(String[]::new);
        String[] commands = {"-doclet", Doclet.class.getName(),// 文档类
                "-encoding", Charset.defaultCharset().displayName(), // 编码
                "-classpath", System.getProperty("java.class.path") + ";" + buildInfo // 编译依赖路径
        };
        Main.execute(ArrayUtil.addAll(commands, sourceJavaArr));
    }

    /**
     * 收集方法相关源数据
     * List<Tuple>  => 0 类全名,  1 父类全名,  2 方法名, 3 方法第一个参数类型, 4 方法第一行注释, 5 方法参数个数>1
     * ListValueMap<String, String>   =>  类的 继承关系
     */
    private static void metaInfoCollect(List<Tuple> rulesMetaInfo, ListValueMap<String, String> clazzInheritMap) {
        for (ClassDoc classDoc : rootDoc.classes()) {
            MethodDoc[] methods = classDoc.methods();
            String clazzName = classDoc.toString();
            ClassDoc superclass = classDoc.superclass();
            if (null == superclass) {
                LOG.debug("{} 不为具体实现类将跳过规则元数据收集!", classDoc);
                continue;
            }
            String superClazzName = superclass.toString();
            int actRule = 0;
            for (MethodDoc method : methods) {
                // 非public 非static 已注解标注过期 配置文件指定过滤 的方法不 生成 postfix规则  ( 方法参数为0 也要过滤, 因为 postfix 是通过参数来 匹配规则提示的)
                Parameter[] parameters = method.parameters();
                if (!method.isPublic() || !method.isStatic() || isDeprecated(method) || parameters.length == 0) {
                    continue;
                }
                // 收集方法相关源数据
                rulesMetaInfo.add(new Tuple(clazzName, superClazzName, method.name(), parameters[0].type().toString(), commentText(method), parameters.length > 1));
                actRule++;
            }
            if (actRule == 0) {
                LOG.debug("{} 实现类 无可用静态方法!", classDoc);
            }
            // 收集父子类 关系
            if (INHERIT_PARENT_METHOD && !OBJ_CLASS_NAME.equals(superClazzName)) {
                clazzInheritMap.putValue(superClazzName, clazzName);
            }
        }
    }

    /**
     * 收集 方法注解 第一行描述
     */
    private static String commentText(MethodDoc method) {
        String commentText = method.commentText().split("\n", 2)[0].trim()// 取第一行注释
                .replaceAll("</?\\w+?>", "")//
                .replaceAll("[:：,，。；！.]$", "") //
                .replace("，", ",").replace("：", ":")//
                .replace("“", "\"").replace("”", "\"").replace("&quot;", "\"")//
                .replace("（", "(").replace("）", ")")//
                .replaceAll("\\{@\\w+\\s+(\\w+)}", "$1")// {@xxxxx} 长度不多,不用替换
                .replaceAll("\\s*", "")//
                .trim();//
        return Config.RULE_COMMENT_PREFIX + (CharSequenceUtil.isBlank(commentText) ? method.name() : commentText); // 通用前缀 + 方法第一行注释
    }

    /**
     * 收集 父类方法的信息
     */
    private static void addSuperInfo(String clazzName, Set<Tuple> methodInfos, Map<String, Set<Tuple>> clazzMap, ListValueMap<String, String> clazzInheritMap) {
        Optional<String> first = clazzInheritMap.entrySet().stream().filter(e -> e.getValue().contains(clazzName)).map(Map.Entry::getKey).findFirst();
        if (!first.isPresent()) {
            return;
        }
        String superClass = first.get();
        Set<Tuple> tuples = clazzMap.get(superClass);
        if (null != tuples) {
            methodInfos.addAll(tuples);
        }
        addSuperInfo(superClass, methodInfos, clazzMap, clazzInheritMap);
    }

    private static boolean isDeprecated(MethodDoc method) {
        return Arrays.stream(method.annotations()).anyMatch(doc -> doc.toString().contains("@java.lang.Deprecated"));
    }

    public static class Doclet {
        private Doclet() {
            //do nothing
        }

        // 必须含 static start 方法  (Doclet 文档)
        public static boolean start(RootDoc rootDoc) {
            RuleDocletGenerator.rootDoc = rootDoc;
            return true;
        }
    }
}
